//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<expose_app_to_python.cpp>
///	@path	~/src/pyxeres/expose/
///	@date	2008/11/05
///	@desc	.

#define _FULLAPI
#include "config/config.h"

#include "database/DataManager.h"
#include "database/store/IFileSystem.h"
#include "database/xres/CLocalFileSystem.h"
#include "database/xres/CXmlFileHandler.h"
#include "database/xdata/CDirectoryData.h"
#include "database/xdata/CTreeData.h"

#include "graphics/dx9/xeres_dx9.h"
#include "graphics/dx10/xeres_dx10.h"
#include "graphics/dx9/import_dx9.h"
#include "graphics/IRenderer.h"
#include "graphics/canvas/CCanvasProxy.h"

#include "scene/IScene.h"
#include "scene/ICamera.h"
#include "scene/camera/CCameraBase.h"
#include "scene/xsimple/XSimple.h"

#include "engine/xcore/xeres_engine.h"
#include "engine/xcore/event_center.h"
#include "engine/xcore/CInputEvent.h"

#include "python/python_plug.h"
#include "python/interpreter/CPythonInterpreter.h"
#include "python/wrap/expose_python.h"

#include "gui/xgui/XCtrls.h"
#include "gui/xgui/XCtrlsExt.h"
#include "gui/xgui/XEditCtrl.h"
#include "gui/xgui/XTagLayout.h"

#include "python/py_boost_decl.h"
#include "python/python_plug.h"
#include "python/wrap/expose_decl_python.h"

#include "appear/app/input_handler.h"
#include "appear/app/message_filter.h"
#include "appear/app/ticker.h"
#include "appear/app/key_map.h"
#include "appear/xwin/GameApp.h"
#include "appear/xwin/GameDebug.h"
#include "appear/xwin/UiManager.h"
#include "appear/xwin/DefaultInputAdaptor.h"
#include "appear/xwin/DirectInputAdapter.h"
#include "appear/xwin/RegularGameMessage.h"
#include "appear/xwin/InputReflection.h"
#include "appear/xwin/ShellConsole.h"
#include "appear/xwin/OutlineConsole.h"
#include "appear/xwin/ProfileConsole.h"

void active_loop( boost::function<void()> fn );

namespace xeres {

	namespace
	{
		// instance flag
		static bool s_pyGameAppRunning = false;

		// application name.
		static const WString APP_NAME = _S("xeres_pyxeres");
		static const WString APP_TITLE = _S("pyx - xeres in python");

		/*! 
			\class	PyGameApp
			\brief	Python application wrapper.
		*/
		class PyGameApp : public GameApp
		{
		public:

			/// \ctor
			PyGameApp( void )
				: m_hWnd( NULL )
				, m_inputTicker( NULL )
				, m_inputHandler( NULL )
				, m_inputDispatcher( NULL )
				, m_inputFilter( NULL )
				, m_regularFilter( NULL )
				, m_debugger( NULL )
				, m_title( APP_TITLE )
				, m_x( 100 )
				, m_y( 100 )
				, m_width( 800 )
				, m_height( 600 )
			{
			}

			/// \dtor
			virtual ~PyGameApp( void )
			{
				TRACE_INFO( _S(".Finalize PyGameApp...") );
			}

		public:

			// This callback registers extension classes.
			static void _init_reg_class( void )
			{
				TRACE_INFO( _S(".Init: Registering class...") );
				// Register DX9 classes.
				LoadDirectX9();
				IOBJECT_REGISTER_CLASS( XSimple );
			}

			// This callback initializes graphics device.
			// We use the class we just registered to create rendering environment.
			static void _init_device( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Initializing device...") );

				Ref<IData> entry;
				// Create new graphics device by passing class name.
				app->engine().ConfigDevice( app->m_hWnd , _S("CDevice9") , _S("CRenderer9") , entry , entry );
				// Set default shading mode.
				app->engine().ChangeShadingMode( _S("forward") );
			}

			// This call back initializes scene manager.
			static void _init_scene_mgr( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Constructing scene...") );

				Ref<IData> entry;
				// Initializes scene manager.
				app->engine().ConfigScene( entry );
			}

			// This call config multi-tasking of engine.
			static void _init_multi_tasking( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Configurating multi-tasking...") );

				Ref<IData> entry;
				// Set tasking config.
				app->engine().ConfigMultiTasking( entry );
			}

			// This callback creates input handlers, and take control of input event dispatching.
			static void _init_input( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Preparing input...") );

				// By passing event class name, engine creates events and place them in event center.
				((InputReflection*)(app->m_inputHandler))->ConnectEngine( app->engine() );

				// Creates subscriber, and subscribe inputs, in order to receive input event.
				app->engine().SubscribeEvent(
					_S("input/keyboard") , EVENTSLOT_APP , bind( _on_key_input , app , _1 ) );

				app->engine().SubscribeEvent(
					_S("input/mouse") , EVENTSLOT_APP , bind( _on_mouse_input , app , _1 ) );
			}

			// This callback initializes window position and status.
			static void _init_window( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Ajusting window...") );

				::SetWindowPos( app->m_hWnd , HWND_TOP , 0 , 0 , 0 , 0 , SWP_NOMOVE|SWP_NOREDRAW|SWP_NOSIZE );
				::SetWindowLong( app->m_hWnd , GWL_STYLE , WS_OVERLAPPEDWINDOW );
					//WS_CAPTION|WS_OVERLAPPED|WS_SYSMENU|WS_MINIMIZEBOX );

				// Connect regular message as event.
				((RegularGameMessage*)app->m_regularFilter)->ConnectEngine( app->engine() );

				// Subscribe regular game messages.
				app->engine().SubscribeEvent(
					_S("message/activate") , EVENTSLOT_APP , bind( _activate_window_call , app , _1 ) );

				app->engine().SubscribeEvent(
					_S("message/size") , EVENTSLOT_APP , bind( _size_window_call , app , _1 ) );

				app->engine().SubscribeEvent(
					_S("message/paint") , EVENTSLOT_APP , bind( _paint_window_call , app , _1 ) );
			}

			// This callback initializes gui manager.
			static void _init_gui( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Building GUI...") );

				// Create GUI Manager.
				app->m_ui.Initialize( app->m_hWnd , app->m_inputDispatcher , app->engine() );
			}

			// This callback shows window while initialization is at final stage.
			static void _init_final_window( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Showing window...") );

				// Setup overlays.
				app->engine().SetOverlaySlot( 3 );

				// bottom layer, GUI elements.
				app->engine().SetOverlayCanvas( 0 , new CCanvasProxy( bind( &UiManager::Render , app->m_ui , _1 ) ) );

				// middle layer, debug components.
				if( app->m_debugger )
					app->engine().SetOverlayCanvas( 1 , new CCanvasProxy( bind( &GameDebug::Render , app->m_debugger , _1 ) ) );

				// top layer, engine debug.
				app->engine().SetOverlayCanvas( 2 , new CCanvasProxy( bind( &EngineDebug::Render , &app->engine() , _1 ) ) );

				::ShowWindow( app->m_hWnd , SW_SHOW );
				::UpdateWindow( app->m_hWnd );
			}

			// This function creates & initializes script system.
			static void _init_script_system( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Runing script...") );

				// Initialize python.
				PythonPlug::instance().SetEncoding( "mbcs" );

				// Call initialize.
			}

			// This function creates script console
			static void _init_console( PyGameApp * app )
			{
				TRACE_INFO( _S(".Init: Starting console...") );

				if( !app->m_debugger )
					return;

				// Initialize debugger.
				app->m_debugger->Initialize();

				// Create python console.
				ShellConsole * shell_console = new ShellConsole;
				shell_console->Initialize();

				Ref<CPythonInterpreter> python_interp = new CPythonInterpreter;
				shell_console->AddInterpreter( _S("python") , python_interp );

				// Register console.
				app->m_debugger->RegisterConsole( _S("ShellConsole") , shell_console );

				// Register key map.
				app->m_hotkey.SetKey( Input::INPUT_STATE_CTRL , KeyInput::KEY_P ,
					bind( &_active_console , app , shell_console , _S("python") ) );

				// Create profile console.
				ProfileConsole * profile_console = new ProfileConsole;
				profile_console->Initialize( NULL );

				app->m_debugger->RegisterConsole( _S("ProfileConsole") , profile_console );

				// Register key map.
				app->m_hotkey.SetKey( Input::INPUT_STATE_CTRL , KeyInput::KEY_F ,
					bind( &GameDebug::ActiveConsole , app->m_debugger , _S("ProfileConsole") ) );
			}

			// finalize script
			static void _fini_script_system( PyGameApp * app )
			{
			}

			// finalize console
			static void _fini_console( PyGameApp * app )
			{
				// finalize console
				app->m_debugger->Finalize();
			}

			// finalize gui manager
			static void _fini_gui( PyGameApp * app )
			{
				// finalize python
				app->m_ui.Finalize();
			}

			// active debug console
			static void _active_console( PyGameApp * app , ShellConsole * console , const wchar_t * interpreter )
			{
				if( !app->m_debugger )
					return;

				if( console->GetActiveInterpreter() != interpreter )
				{
					console->ActiveInterpreter( interpreter );
				}
				else
				{
					app->m_debugger->ActiveConsole( _S("ShellConsole") );
					return;
				}
				if( app->m_debugger->GetActiveConsole() != console )
					app->m_debugger->ActiveConsole( _S("ShellConsole") );
			}

			// This function handles key input event.
			static HandleEvent _on_key_input( PyGameApp * app , void * input )
			{
				KeyInput * key = static_cast<KeyInput*>(input);

				// Hack of closing short cut.
				if( key->GetKey() == KeyInput::KEY_F4 && key->IsAltDown() )
				{
					// Should force to quit.
					::PostMessage( app->m_hWnd , WM_CLOSE , 0 , 0 );
					return EVENT_HANDLED;
				}

				if( app->m_hotkey.OnKeyInput( *key ) == INPUT_HANDLED )
					return EVENT_HANDLED;

				if( app->m_debugger && app->m_debugger->OnKeyInput( *key ) == INPUT_HANDLED )
					return EVENT_HANDLED;

				if( app->m_ui.OnKeyInput( *key ) == INPUT_HANDLED )
					return EVENT_HANDLED;

				return EVENT_UNHANDLED;
			};

			// This function handles mouse input event.
			static HandleEvent _on_mouse_input( PyGameApp * app , void * input )
			{
				MouseInput * mouse = static_cast<MouseInput*>(input);

				if( app->m_debugger && app->m_debugger->OnMouseInput( *mouse ) == INPUT_HANDLED )
					return EVENT_HANDLED;

				if( app->m_ui.OnMouseInput( *mouse ) == INPUT_HANDLED )
					return EVENT_HANDLED;

				return EVENT_UNHANDLED;
			};

			// Activate window callback.
			static HandleEvent _activate_window_call( PyGameApp * app , void * msg )
			{
				WindowEventInfo * info = static_cast<WindowEventInfo*>(msg);
				if( info->m_tag == WINDOW_EVENT_ACTIVATE )
				{
					app->engine().SetFrameLimit( 0 );
				}
				else if( info->m_tag == WINDOW_EVENT_INACTIVATE )
				{
					app->engine().SetFrameLimit( 1 );
				}
				else
				{
					return EVENT_UNHANDLED;
				}
				return EVENT_HANDLED;
			}

			// Size change callback.
			static HandleEvent _size_window_call( PyGameApp * app , void * msg )
			{
				WindowEventInfo * info = static_cast<WindowEventInfo*>(msg);
				static bool is_sizing = false;

				if( info->m_tag == WINDOW_EVENT_ENTERSIZING )
				{
					app->engine().DeviceOnEnterSizing( app->m_hWnd );

					is_sizing = true;
				}
				else if( info->m_tag == WINDOW_EVENT_EXITSIZING )
				{
					app->engine().DeviceOnExitSizing( app->m_hWnd );

					// UI resize.
					app->m_ui.OnSize( app->engine() , Pair<int>( info->m_rect.width() , info->m_rect.height() ) );

					is_sizing = false;
				}
				else if( info->m_tag == WINDOW_EVENT_MINIMIZED )
				{
					app->engine().DeviceOnSize( app->m_hWnd , true );
				}
				else // WINDOW_EVENT_NONE
				{
					app->engine().DeviceOnSize( app->m_hWnd , false );

					// UI resize.
					if( !is_sizing )
						app->m_ui.OnSize( app->engine() , Pair<int>( info->m_rect.width() , info->m_rect.height() ) );
				}
				return EVENT_HANDLED;
			}

			// Paint callback.
			static HandleEvent _paint_window_call( PyGameApp * app , void * msg )
			{
				::ValidateRect( app->m_hWnd , NULL );
				return EVENT_UNHANDLED;
			}

			// UI update tick.
			static void _update_ui( PyGameApp * app , const Frame& frame )
			{
				app->m_ui.Tick( frame );
			}

			/// \brief	Initialize application.
			virtual bool Initialize( void )
			{
				// Create objects.
				m_inputHandler = new InputReflection;
				// 2 ways of handling input.
				DefaultInputAdaptor * adaptor = new DefaultInputAdaptor( m_inputHandler );
				m_inputFilter = adaptor;
				m_inputDispatcher = adaptor;
				m_regularFilter = new RegularGameMessage;

				if( m_enableDebug )
					m_debugger = new GameDebug;

				// ----Initialization----

				// 1. Register classes.
				engine().QueueInitializer( _S("./register/xdemo") , _S("class") ,
					_init_reg_class );

				// 2. Initialize window position & style.
				engine().QueueInitializer( _S("./window") , _S("register") ,
					bind( _init_window , this ) );

				// 3. Initialize device.
				engine().QueueInitializer( _S("./device") , _S("window") ,
					bind( _init_device , this ) );

				// 4. Initialize GUI manager.
				engine().QueueInitializer( _S("./gui") , _S("device") ,
					bind( _init_gui , this ) );

				// 5. Initialize scene manager.
				engine().QueueInitializer( _S("./scene") , _S("gui") ,
					bind( _init_scene_mgr , this ) );

				// 6. Initialize multi-tasking.
				engine().QueueInitializer( _S("./thread") , _S("scene") ,
					bind( _init_multi_tasking , this ) );

				// 7. Initialize input.
				engine().QueueInitializer( _S("./input") , _S("thread") ,
					bind( _init_input , this ) );

				// 8. Initialize script system.
				engine().QueueInitializer( _S("./script") , _S("input") ,
					bind( _init_script_system , this ) );

				// 9. Initialize console.
				engine().QueueInitializer( _S("./console") , _S("script") ,
					bind( _init_console , this ) );

				// 10. Finally, we show window.
				engine().QueueInitializer( _S("final/show") , _S("") ,
					bind( _init_final_window , this ) );

				// ----Finalization----

				// 1. Finalize console.
				engine().QueueFinalizer( _S("~/console") , _S("") ,
					bind( _fini_console , this ) );

				// 2. Finalize script system.
				engine().QueueFinalizer( _S("~/script") , _S("console") ,
					bind( _fini_script_system , this ) );

				// 3. Finalize gui manager.
				engine().QueueFinalizer( _S("~/gui") , _S("script") ,
					bind( _fini_gui , this ) );

				// ----Update----
// 				engine().InsertStage( _S("logic/xdemo") , _S("") ,
// 					bind( _update_logic , this , _1 ) );

				engine().InsertStage( _S("ui/update") , _S("") ,
					bind( _update_ui , this , _1 ) );

				// ----Application----

				// Add input & regular message filter to application.
				AddFilter( _S("input") , _S("") , m_inputFilter );
				AddFilter( _S("regular") , _S("input") , m_regularFilter );

				// Some input needs tick callback, just add it if necessary.
				if( m_inputTicker )
					AddTicker( _S("input") , _S("") , m_inputTicker );

				return true;
			}

			// finalize
			virtual void Finalize( void )
			{
				// Cleanup created objects
				delete m_regularFilter;
				m_regularFilter = NULL;
				delete m_inputFilter;
				m_inputFilter = NULL;
				delete m_inputHandler;
				m_inputHandler = NULL;

				delete m_inputFilter;
				m_inputFilter = NULL;

				if( m_debugger )
				{
					delete m_debugger;
					m_debugger = NULL;
				}

				m_hotkey.ClearAll();

				TRACE_INFO( _S("Exit XDemo Application...") );
			}

			/// \brief	Run application.
			void MainLoop( void )
			{
				PyGILState_STATE gstate;
				gstate = PyGILState_Ensure();

				s_pyGameAppRunning = true;
				{
					HINSTANCE hInstance = (HINSTANCE)::GetModuleHandle( NULL );

					// Initialize GDI+.
					Gdiplus::GdiplusStartupInput gdiplusStartupInput;
					memset( &gdiplusStartupInput , 0 , sizeof(Gdiplus::GdiplusStartupInput) );

					gdiplusStartupInput.GdiplusVersion = 1;
					gdiplusStartupInput.DebugEventCallback = NULL;
					gdiplusStartupInput.SuppressBackgroundThread = FALSE;
					gdiplusStartupInput.SuppressExternalCodecs = FALSE;

					ULONG_PTR gdiplusToken;
					Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

					// Initialize COM.
					HRESULT hRes = ::CoInitialize(NULL);

					// Register window class
					WNDCLASSEX wcex = {
						sizeof(WNDCLASSEX) ,
						CS_HREDRAW | CS_VREDRAW ,
						(WNDPROC) DefWindowProc ,
						0 , 0 , hInstance ,
						::LoadIcon(NULL,IDI_APPLICATION) ,
						::LoadCursor(NULL,IDC_ARROW) ,
						(HBRUSH)(COLOR_WINDOW+1) ,
						NULL , APP_NAME.c_str() ,
						::LoadIcon(NULL,IDI_APPLICATION)
					};

					// Register window class.
					if( !::RegisterClassEx(&wcex) )
					{
						__win32_error( _S("Failed to register window class : %s.") );
						FATAL_ABORT( _S("Failed to register window") );
					}

					// Create game window
					m_hWnd = ::CreateWindow(
						APP_NAME.c_str() ,
						m_title.c_str() ,
						WS_POPUP | WS_BORDER ,
						m_x , m_y , m_width , m_height ,
						NULL ,
						NULL ,
						hInstance ,
						0 );

					if( m_hWnd == NULL )
					{
						__win32_error( _S("Failed to create window : %s.") );
						FATAL_ABORT( _S("Failed to create window") );
					}

					// Game application run
					GameApp::Run( m_hWnd );

					// Game end
					::DestroyWindow( m_hWnd );
					m_hWnd = NULL;

					// Unregister window class
					::UnregisterClass( APP_NAME.c_str() , hInstance );

					// Uninitialize com.
					if( SUCCEEDED(hRes) )
						::CoUninitialize();

					// shut down GDI+
					Gdiplus::GdiplusShutdown(gdiplusToken);
				}
				s_pyGameAppRunning = false;

				PyGILState_Release(gstate);

				// reset last state
				//track_scope_clear_all();
			}

			/// \brief	Set application title.
			void SetTitle( const WString& title )
			{
				m_title = title;
				if( m_hWnd )
					::SetWindowText( m_hWnd , m_title.c_str() );
			}

			/// \brief	Set window size.
			void SetSize( int width , int height )
			{
				m_width = width;
				m_height = height;
				if( m_hWnd )
					::SetWindowPos( m_hWnd , NULL , 0 , 0 , width , height , SWP_NOMOVE );
			}

			/// \brief	Set window pos.
			void SetPos( int x , int y )
			{
				m_x = x;
				m_y = y;
				if( m_hWnd )
					::SetWindowPos( m_hWnd , NULL , x , y , -1 , -1 , SWP_NOSIZE );
			}
			
			/// \brief	Set enable debug.
			void SetEnableDebug( bool enable )
			{
				m_enableDebug = int(enable);
			}

		private:

			//@ data

			// window handle
			HWND				m_hWnd;
			WString				m_title;
			int					m_x , m_y;
			int					m_width;
			int					m_height;
			
			int					m_enableDebug;

			// input handler	
			InputHandler *		m_inputHandler;
			InputDispatcher *	m_inputDispatcher;
			MessageFilter *		m_inputFilter;
			MessageFilter *		m_regularFilter;
			Ticker *			m_inputTicker;

			// debugger
			GameDebug *			m_debugger;

			// ui manager
			UiManager			m_ui;

			// application hot key
			KeyMap				m_hotkey;
		};

		/// \brief	Constructor wrap.
		PyGameApp * construct_pyapp_wrap( void )
		{
			// Skip if game app is running.
			if( s_pyGameAppRunning )
			{
				// FIXME! should raise an exception.
				return NULL;
			}
			return new PyGameApp;
		}

		/// \brief	Main loop Wrap.
		void MainLoopWrap( PyGameApp * app )
		{
			active_loop( bind( &PyGameApp::MainLoop , app ) );
		}
	}

	static void expose_app( void )
	{
		class_< PyGameApp , noncopyable >( "PyGameApp" )
			// ctor
			.def( "__init__" , make_constructor( &construct_pyapp_wrap ) )
			// main loop
			.def( "main" , &MainLoopWrap )
			// methods
			.def( "set_title" , &PyGameApp::SetTitle )
			.def( "set_pos" , &PyGameApp::SetPos )
			.def( "set_size" , &PyGameApp::SetSize )
			.def( "enable_debug" , &PyGameApp::SetEnableDebug )
		;
		register_ptr_to_python<PyGameApp*>();
	}

	void expose_app_to_python( void )
	{
		expose_app();
	}
	
} // namespace xeres
